import 'dart:collection';
import 'dart:convert';

import 'package:flutter/services.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:lib_database/src/config.dart';

import 'package:lib_database/lib_database.dart';
import 'package:lib_util/lib_util.dart';
import 'package:lib_util/log/log.dart';

import '../widgets/dialog/easyloading.dart';
import 'chains_type/ethchain.dart';

class JsCallback {
  final bool showError;
  final void Function(dynamic) callback;
  final void Function(String) errorCallback;

  JsCallback({required this.callback, required this.errorCallback, this.showError = true});
}

class Web3JsCall {
  Web3JsCall._privateConstructor();

  static final Web3JsCall _instance = Web3JsCall._privateConstructor();

  factory Web3JsCall() {
    return _instance;
  }

  HeadlessInAppWebView? _headlessWebView;
  InAppWebViewController? _webViewController;

  InAppWebViewController get controller => _webViewController!;

  final Map<int, JsCallback> _callJs = {};

  int _requestId = 0;

  int callJsWaiting(JsCallback callback) {
    int id = _requestId++;
    _callJs[id] = callback;
    return id;
  }

  void removeRequestId(int id) {
    _callJs.remove(id);
  }

  //  查询 erc20  主币 余额
  int jsErc20MainPriceU(String chainId, String? rpc, JsCallback callback) {
    int id = callJsWaiting(callback);
    String? swapToContract = Config.defiLlamaChainNames[chainId.toString()]!.length > 0
        ? null
        : '\'${Config.getChainUSDTContract[chainId.toString()]}\'';
    print("swapToContract $swapToContract");
    controller.evaluateJavascript(source: 'getErc20MainCurrencyPriceU($id,$chainId,$swapToContract,\'$rpc\')');
    return id;
  }

  //  查询 erc20  Gas
  int jsErc20GasFee(String type, String rpc, String from, String token, JsCallback callback) {
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(source: 'getERC20Gas($id,\'$type\',\'$rpc\',\'$from\',\'$token\')');
    return id;
  }

  //  查询 erc20  blockHeight
  int getERC20BlockHeightByRpc(String rpc, JsCallback callback) {
    int id = callJsWaiting(callback);

    controller.evaluateJavascript(source: 'getERC20BlockHeightByRpc($id,\'$rpc\')');
    return id;
  }

  //  查询 erc20 代币对 U  主币 余额 getTokenCurrencyPriceOtherToken({
  //         requestId, chainId, tokenFromAddress, tokenToAddress, rpc
  //     })
  int getTokenCurrencyPriceOtherTokenPriceU(
      {required String chainId,
      String? rpc,
      required String tokenFromAddress,
      required String tokenToAddress,
      required JsCallback callback}) {
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(
        source:
            'getTokenCurrencyPriceOtherToken({requestId:$id,chainId:$chainId,tokenFromAddress:\'$tokenFromAddress\',tokenToAddress:\'$tokenToAddress\',rpc:\'${rpc == null ? rpc : "$rpc"}\'})');
    return id;
  }

  //  查询 erc20  代币信息 余额
  int jsErc20TokenContractInfo(
      String chainId, String contractAddress, String rpc, JsCallback callback, bool? isString) {
    int id = callJsWaiting(callback);
    Map<String, dynamic> params = {
      "requestId": id,
      "chainId": "$chainId",
      "contractAddress": "$contractAddress",
      "rpc": "$rpc"
    };
    print('contractAddress  $contractAddress');
    Log().d('getErc20TokenInfo(${jsonEncode(params)})');
    controller.evaluateJavascript(source: 'getErc20TokenInfo(${jsonEncode(params)})');
    return id;
  }

  int jsEthSendTransaction(
      String pk, ETHTransaction transaction, String rpc, bool waitForReceipt, JsCallback callback) {
    String jsonTransaction = json.encode(transaction);
    Config.logger.d(jsonTransaction);
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(
        source: 'ethSendTransaction($id,"$pk",\'$jsonTransaction\',\'$rpc\',$waitForReceipt)');
    return id;
  }

  int jsEthSendTransaction2(String type, String contractAddress, String pk, ETHTransaction transaction, String rpc,
      bool waitForReceipt, JsCallback callback) {
    String jsonTransaction = json.encode(transaction);
    Config.logger.d(jsonTransaction);
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(
        source:
            'ethSendTransaction2($id,"$type",\'${contractAddress}\',"$pk",\'$jsonTransaction\',\'$rpc\',$waitForReceipt)');
    return id;
  }

  int jsEthSingSend(Map<String, dynamic> transaction, String sginStr, int chainId, String rpc, bool waitForReceipt,
      JsCallback callback) {
    String jsonTransaction = json.encode(transaction);
    Config.logger.d(jsonTransaction);
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(
        source: 'ethSignSendTransaction($id,\'$jsonTransaction\',\'$sginStr\',\'$chainId\',\'$rpc\',$waitForReceipt)');
    return id;
  }

  int jsEthSignData(String signData, JsCallback callback) {
    Config.logger.d(signData);
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(source: 'ethSignData($id,\'${signData}\')');
    return id;
  }

  int jsEthSendTx(
      String type, String contractAddress, ETHTransaction transaction, int chainId, String rpc, JsCallback callback) {
    String jsonTransaction = json.encode(transaction);
    Config.logger.d(jsonTransaction);
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(
        source: 'ethSendTx($id,"$type",\'${contractAddress}\',\'$jsonTransaction\',\'$chainId\',\'$rpc\')');
    return id;
  }

  int jsEstimateGasFee(String rpc, String transaction, JsCallback callback) {
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(source: 'ethEstimateGasFee($id,"$rpc",\'$transaction\')');
    return id;
  }

  int jsEthBuildSendTokenTransaction(
      {required String type,
      required String rpc,
      required String from,
      required String to,
      required String token,
      required String amount,
      required JsCallback callback,
      String? tokenId,
      String? gas,
      String? gasPrice}) {
    gas ??= '';
    gasPrice ??= '';
    tokenId ??= '';
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(
        source: 'ethBuildSendTokenTransaction($id,'
            '"$type", "$rpc", "$from", "$to",'
            '"$token", "$tokenId",  "$amount","$gas", "$gasPrice")');
    return id;
  }

  int jsCheckOmpSwap({
    required String rpc,
    required String address,
    required String tokenA,
    required String tokenB,
    required String amountA,
    required int version,
    required JsCallback callback,
  }) {
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(
        source: 'checkOmpSwap($id, "$rpc", "$address", "$tokenA", "$tokenB", "$amountA",$version)');
    return id;
  }

  int jsEthBuildBatchSendNFTTransaction(
      {required String type,
      required String rpc,
      required String batchContract,
      required String from,
      required String to,
      required String token,
      required List<String> tokenIds,
      required List<int> amounts,
      required JsCallback callback,
      String? gas,
      String? gasPrice}) {
    gas ??= '';
    gasPrice ??= '';
    // Config.logger.d(
    //     'ethBuildBatchSendNFTTransaction(${callJsWaiting(callback)}, "$type", "$rpc", "$batchContract", "$from", "$to", "$token", "${tokenIds.join(',')}", "${amounts.join(',')}", "$gas", "$gasPrice")');
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(
        source:
            'ethBuildBatchSendNFTTransaction($id, "$type", "$rpc", "$batchContract", "$from", "$to", "$token", "${tokenIds.join(',')}", "${amounts.join(',')}", "$gas", "$gasPrice")');
    return id;
  }

  int jsBuildOmpSwapTransaction(
      {required String rpc,
      required String address,
      required String tokenA,
      required String tokenB,
      required String amountA,
      required String amountB,
      required int version,
      required JsCallback callback}) {
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(
        source:
            'buildOmpSwapTransaction($id, "$rpc", "$address", "$tokenA", "$tokenB", "$amountA", "$amountB",$version)');
    return id;
  }

  int jsBuildAnySwapTransaction(
      {required String rpc,
      required String token,
      required String contract,
      required String fromAddress,
      required String crossAddress,
      required String amount,
      required JsCallback callback}) {
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(
        source:
            'buildAnySwapTransaction($id, "$rpc", "$token", "$contract", "$fromAddress", "$crossAddress", "$amount")');
    return id;
  }

  int jsEthBuildOmpBridgeTransaction(
      {required String rpc,
      required String contract,
      required String fromAddress,
      required String crossAddress,
      required String amount,
      required JsCallback callback}) {
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(
        source: 'ethBuildOMPBridgeTransaction($id, "$rpc", "$contract", "$fromAddress", "$crossAddress", "$amount")');
    return id;
  }

  int jsCheckAnySwapInfo(
      {required String sellRpc,
      required String buyRpc,
      required String sellToken,
      required String buyToken,
      required String anySwapData,
      required String walletAddress,
      required JsCallback callback}) {
    Config.logger.d(
        'sellRpc:$sellRpc,buyRpc:$buyRpc,sellToken:$sellToken,buyToken:$buyToken,anySwapData:$anySwapData,wallet:$walletAddress');
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(
        source: 'checkAnySwapInfo($id, "$sellRpc","$buyRpc","$sellToken","$buyToken","$anySwapData","$walletAddress")');
    return id;
  }

  int jsBindSuperiorAddress(
      {required String rpc,
      required String inviters,
      required String wallet,
      required String father,
      required JsCallback callback}) {
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(source: 'bindSuperiorAddress($id,"$rpc","$inviters","$wallet","$father")');
    return id;
  }

  int jsCheckDexInfo(
      {required String rpc,
      required String dex,
      required String wallet,
      required int pairId,
      required int priceDepthDecimals,
      required int maxLen,
      required JsCallback callback}) {
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(
        source: 'checkOmpDexInfo($id,"$rpc","$dex","$wallet",$pairId,$priceDepthDecimals,$maxLen)');
    return id;
  }

  int jsBuildDexTransaction(
      {required String rpc,
      required String dex,
      required String dexDelegate,
      required String wallet,
      required int pairId,
      required String price,
      required String amount,
      required int dexType,
      required int tradeType,
      required JsCallback callback}) {
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(
        source:
            'buildDexTransaction($id,"$rpc","$dex","$dexDelegate","$wallet",$pairId,"$price","$amount",$dexType,$tradeType)');
    return id;
  }

  int jsBuildDexCancelTransaction(
      {required String rpc,
      required String dex,
      required String wallet,
      required String orderId,
      required JsCallback callback}) {
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(source: 'buildDexCancelTransaction($id,"$rpc","$dex","$wallet","$orderId")');
    return id;
  }

  int jsTronSignMessage(String pk, String message, JsCallback callback) {
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(source: 'tronSign($id,\'$pk\',\'$message\',"${Config.tronApiKey}")');
    return id;
  }

  int jsTronBuildSendTokenTransaction(
      {required String type,
      required String from,
      required String to,
      required String token,
      required String amount,
      required JsCallback callback,
      String? tokenId}) {
    tokenId ??= '';
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(
        source:
            'tronBuildSendTokenTransaction($id, "$type", "$from", "$to", "$token", "$tokenId", "$amount","${Config.tronApiKey}")');
    return id;
  }

  int jsTronBuildOmpBridgeTransaction(
      {required String rpc,
      required String contract,
      required String fromAddress,
      required String crossAddress,
      required String amount,
      required JsCallback callback}) {
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(
        source:
            'tronBuildOmpBridgeTransaction($id, "$rpc", "$contract", "$fromAddress", "$crossAddress", "$amount","${Config.tronApiKey}")');
    return id;
  }

  int jsTronSendTransaction(
      {required String pk, required String transaction, required JsCallback callback, bool waitForReceipt = false}) {
    Config.logger.d('pk:$pk,transaction:$transaction');
    int id = callJsWaiting(callback);
    controller.evaluateJavascript(
        source: 'tronSendTransaction($id, "$pk", \'$transaction\',"${Config.tronApiKey}",$waitForReceipt)');
    return id;
  }

  init() async {
    String web3js = await rootBundle.loadString('assets/chaincore/web3.min.js');
    String tronWebJs = await rootBundle.loadString('assets/chaincore/tronweb.js');
    _headlessWebView ??= HeadlessInAppWebView(
        initialOptions: InAppWebViewGroupOptions(
          crossPlatform: InAppWebViewOptions(
              // 允许跨域请求
              ),
        ),
        initialFile: 'assets/chaincore/wtools.html',
        initialUserScripts: UnmodifiableListView<UserScript>([
          UserScript(
              source: 'window.Kai={'
                  'postMessage:function(json){window.flutter_inappwebview.callHandler("postMessage",json)},'
                  'ethTransactionResult:function(json){window.flutter_inappwebview.callHandler("ethTransactionResult",json)}'
                  '}',
              injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START),
        ]),
        onConsoleMessage: (controller, consoleMessage) {
          Config.logger.d(consoleMessage.message);
        },
        onLoadStop: (controller, uri) async {
          await _webViewController?.evaluateJavascript(source: web3js);
          await _webViewController?.evaluateJavascript(source: tronWebJs);
        },
        onWebViewCreated: (controller) {
          _webViewController = controller;

          _webViewController!.addJavaScriptHandler(
              handlerName: 'postMessage',
              callback: (args) {
                Config.logger.d(args[0]);
                var resp = json.decode(args[0]);
                if (null == resp) return;
                var id = resp['id'];
                var func = _callJs[id];
                if (id == null || func == null) return;

                _callJs.remove(id);

                var error = resp['error'];
                if (null != error) {
                  func.errorCallback(error);
                  if (func.showError) {
                    EasyLoading.showError(error);
                  }
                  return;
                }

                var data = resp['data'];
                func.callback(data);
              });

          _webViewController!.addJavaScriptHandler(
              handlerName: 'ethTransactionResult',
              callback: (args) {
                Config.logger.d(args[0]);
                var resp = json.decode(args[0]);
                dynamic data = resp['data'];
                if (data is String) {
                  //只有hash,上链了,但是链上执行失败
                  if (null != resp['error']) {
                    // EasyLoading.showError(resp['error']);
                  }
                  // NavKey.navKey.currentContext!.read<TransactionProvider>().removeTransactionHash(data, success: false);
                } else if (data is Map<String, dynamic>) {
                  try {
                    int gas = data['gasUsed'];
                    int gasPrice = data['effectiveGasPrice'];
                    // BigInt gas = BigInt.parse(data['gasUsed'], radix: 16);
                    // BigInt gasPrice = BigInt.parse(data['effectiveGasPrice'], radix: 16);
                    String gasFee = Utils.formatStringBalance(
                        (BigInt.parse((gas * gasPrice).toString()) / BigInt.from(10).pow(18)).toString(),
                        fractionDigits: Config.gasFeeFixedNum,
                        trimDecimalZero: true);
                    int? status = data['status'] ? 1 : 0;

                    String block = data['blockNumber'].toString();
                    String txId = data['transactionHash'];
                    //receipt,上链了,并且链上执行成功
                    ObjectBox().transactionModel.updatePendingTransactionState2(TransactionBean(
                        txId: txId, height: block, txFee: gasFee, state: status.toString(), pending: false));
                  } catch (err) {
                    Log.logger.e(err);
                  }
                } else if (null == data) {
                  //null,未上链,没有hash
                  //会走PostMessage中的error,这里不用处理
                }
              });
        });
    _headlessWebView!.run().then((value) => Config.logger.d('messageheadless webview init success'));
  }
}
